%%%-------------------------------------------------------------------
%%% File    : svcd_srv.erl
%%% Author  :  vjache
%%% Description : Service discovery server.
%%%
%%% Created :  1 Feb 2010 by  vjache
%%%-------------------------------------------------------------------
-module(cluster_srv).

-include("../../include/cluster.hrl").

-behaviour(gen_server).

-define(SERVER,?MODULE).

%% API
-export([start_link/3]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-record(state, {cluster,period,snapshot,timer,evt_mgr}).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Cluster,RefreshPeriod,EventMgrRef) 
  when is_record(Cluster,cluster),is_integer(RefreshPeriod) ->
    gen_server:start_link(
	  cluster:srv_name(Cluster#cluster.name), ?MODULE, [Cluster,RefreshPeriod,EventMgrRef], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Cluster,RefreshPeriod,EventMgrRef]) ->
	cluster:add_listenera(Cluster#cluster.name, fun(Evt)-> io:format("~p~n", [Evt]) end),
	NodeType=visible,
	case net_kernel:monitor_nodes(true, [nodedown_reason,{node_type, NodeType}]) of
		error ->
			{stop,monitor_nodes_failed};
		{error,Reason} ->
			{stop,{monitor_nodes_failed,Reason}};
		ok ->
			% make initial snapshot
%% 			Snap=make_snapshot(Cluster),
			{ok, 
			 refresh_snapshot(#state{
					cluster=Cluster,
					period=RefreshPeriod,
					snapshot=sets:new(),
					timer=erlang:start_timer(RefreshPeriod, self(), make_snapshot),
					evt_mgr=EventMgrRef})}
	end.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(Request, From, State) ->
	try call(Request, From, State) of
		Result -> Result
	catch
		_:Reason ->
			{reply, 
			 {error,{Reason,erlang:get_stacktrace()}}, 
			 State}
	end.
call(get_names,_From,State) ->
    Reply = State#state.snapshot,
    {reply, Reply, State};
call({get_names,PredFun},_From,State) 
  when is_function(PredFun,1)->
    Reply = sets:filter(PredFun, State#state.snapshot),
    {reply, Reply, State};
call({fold_names,FoldFun,Acc0},_From,State) 
  when is_function(FoldFun,2)->
	Reply = sets:fold(FoldFun, Acc0, State#state.snapshot),
    {reply, Reply, State};
call(get_definition,_From,State) ->
    {reply, State#state.cluster, State};
call(get_period,_From,State) ->
    {reply, State#state.period, State};
call(get_event_mgr,_From,State) ->
    {reply, State#state.evt_mgr, State};
call(_Msg,_From,_State) ->
    throw(not_supported).
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(make_snapshot, State) ->
    {noreply, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({nodeup, _Node, _InfoList}, State) ->
	global:sync(),
    {noreply, refresh_snapshot(State)};
handle_info({nodedown, _Node, _InfoList}, State) ->
	global:sync(),
    {noreply, refresh_snapshot(State)};
handle_info({timeout,Timer,make_snapshot}, State) 
  when State#state.timer == Timer ->
	RefreshPeriod=State#state.period,
	State1=refresh_snapshot(State),
	State2=State1#state{
						timer=erlang:start_timer(RefreshPeriod, 
												 self(), 
												 make_snapshot)},
	{noreply,State2};
handle_info(_Msg, State) ->
	{noreply, State}.

refresh_snapshot(State) ->
	Snap0=State#state.snapshot,
	Snap1=make_snapshot(State#state.cluster),
	if
		Snap0==Snap1 ->
			State;
		true ->
			Rem=sets:to_list(sets:subtract(Snap0, Snap1)),
			Add=sets:to_list(sets:subtract(Snap1, Snap0)),
			gen_event:notify(State#state.evt_mgr, {cluster_changed,Add,Rem}),
			State#state{snapshot=Snap1}
	end.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
make_snapshot(Cluster) ->
	Filter=Cluster#cluster.filter_fun,	
	GNames=lists:foldl(
			 fun(GName,Acc)->
					 case Filter(GName) of
						 true -> [GName|Acc];
						 false -> Acc
					 end
			 end, 
			 [], global:registered_names()),
	sets:from_list(GNames).
